﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Linq;
using Suteki.Common.Extensions;
using BulletOnRails4NET.Models;
using System.Collections.Generic;
using System.Linq.Dynamic;

namespace Suteki.Common.Repositories
{
    public abstract class RepositoryFactory
    {
        private static RepositoryFactory _main_instance;
        public DataContext RepositoryDataContext {get; set;}

        public static RepositoryFactory GetInstance()
        {
            //if (_main_instance == null)
            //    _main_instance = new RepositoryFactory();
            return _main_instance;
        }
        public static void SetInstance(RepositoryFactory factory)
        {
            _main_instance = factory;
        }

        protected Dictionary<string, IRepository> Repositories { get; set; }

        public IRepository GetRepository(string class_name)
        {
            return Repositories[class_name];
        }
    }


    public interface IRepository<T> where T : class
    {

        T GetById(int id);

        IQueryable<T> GetAll();
        IQueryable<IDLabelPair> GetAllAsIDLabelPairs();
        IQueryable<IDLabelPair> GetAllAsIDLabelPairs(IQueryable<T> from_query);

        void InsertOnSubmit(T entity);

        void DeleteOnSubmit(T entity);

        //[Obsolete("Units of Work should be managed externally to the Repository.")]

        void SubmitChanges();

    }

    public interface IRepository
    {

        object GetById(int id);

        IQueryable GetAll();
        IQueryable<IDLabelPair> GetAllAsIDLabelPairs();

        void InsertOnSubmit(object entity);

        void DeleteOnSubmit(object entity);

        //[Obsolete("Units of Work should be managed externally to the Repository.")]

        void SubmitChanges();

    }

    public class IDLabelPair
    {
        public int ID { get; set; }
        public string Label { get; set; }
    }

    public class Repository<T> : IRepository<T>, IRepository where T : class
    {

        readonly DataContext dataContext;

        public Repository()
        {
            dataContext = RepositoryFactory.GetInstance().RepositoryDataContext;
        }

        public virtual T GetById(int id)
        {
            var itemParameter = Expression.Parameter(typeof(T), "item");
            var whereExpression = Expression.Lambda<Func<T, bool>>

                (

                Expression.Equal(

                    Expression.Property(

                        itemParameter,

                        typeof(T).GetPrimaryKey().Name

                        ),

                    Expression.Constant(id)

                    ),

                new[] { itemParameter }

                );

            return GetAll().Where(whereExpression).Single();

        }

        public virtual IQueryable<T> GetAll()
        {

            return dataContext.GetTable<T>().AsQueryable();

        }

        public virtual IQueryable<IDLabelPair> GetAllAsIDLabelPairs()
        {
            return GetAllAsIDLabelPairs(GetAll());
        }
        public virtual IQueryable<IDLabelPair> GetAllAsIDLabelPairs(IQueryable<T> from_query)
        {

            return from_query.OrderBy(typeof(T).GetLabelColumn().Name).Select(c =>
        new IDLabelPair()
        {
            ID = (int)typeof(T).GetPrimaryKey().GetValue(c, null),
            Label = typeof(T).GetLabelProperty().GetValue(c, null).ToString()
        }
    );
        }

        public virtual void InsertOnSubmit(T entity)
        {

            GetTable().InsertOnSubmit(entity);

        }

        public virtual void DeleteOnSubmit(T entity)
        {

            GetTable().DeleteOnSubmit(entity);

        }

        public virtual void SubmitChanges()
        {

            dataContext.SubmitChanges();

        }

        public virtual ITable GetTable()
        {
            return dataContext.GetTable<T>();
        }

        IQueryable IRepository.GetAll()
        {

            return GetAll();

        }

        void IRepository.InsertOnSubmit(object entity)
        {

            InsertOnSubmit((T)entity);

        }

        void IRepository.DeleteOnSubmit(object entity)
        {

            DeleteOnSubmit((T)entity);

        }

        object IRepository.GetById(int id)
        {

            return GetById(id);

        }

    }

}